/**
 * @file
 * @author dousha 
 * @date 2020/11/28.
 */

#include "oled.h"
#include "../Logic/convert.h"
#include <string.h>

static I2C_HandleTypeDef *i2c;

const uint8_t oled_init_command[] = {
		OLED_SET_DISP,
		OLED_SET_DISP_CLK_DIV,
		0x80u,
		OLED_SET_MUX_RATIO,
		31u,
		OLED_SET_DISP_OFFSET,
		0x00,
		OLED_SET_DISP_START_LINE,
		OLED_SET_CHARGE_PUMP,
		0x14u,
		OLED_SET_MEM_ADDR,
		0x00u,
		OLED_SET_SEG_REMAP | 0x01u,
		OLED_SET_COM_SCAN_DEC,
		OLED_SET_COM_PIN_CFG,
		0x02u,
		OLED_SET_CONTRAST,
		0x8fu,
		OLED_SET_PRECHARGE,
		0xf1u,
		OLED_SET_VCOM_DESEL,
		0x40u,
		OLED_SET_ENTIRE_ON,
		OLED_SET_NORM_INV,
		OLED_SET_NO_SCROLL,
		OLED_SET_DONE_AND_ENABLE,
};

const static uint8_t bitmask_lut[8][8] = {
		0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff,
		0x03, 0x02, 0x06, 0x0e, 0x1e, 0x3e, 0x7e, 0xfe,
		0x07, 0x06, 0x04, 0x0c, 0x1c, 0x3c, 0x7c, 0xfc,
		0x0f, 0x0e, 0x0c, 0x08, 0x18, 0x38, 0x78, 0xf8,
		0x1f, 0x1e, 0x1c, 0x18, 0x10, 0x30, 0x70, 0xf0,
		0x3f, 0x3e, 0x3c, 0x38, 0x30, 0x20, 0x60, 0xe0,
		0x7f, 0x7e, 0x7c, 0x78, 0x70, 0x60, 0x40, 0xc0,
		0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80
};

const static uint8_t font[100][5] = {
		0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, //
		0b00000000, 0b00000000, 0b01011111, 0b00000000, 0b00000000, // !
		0b00000000, 0b00000011, 0b00000000, 0b00000011, 0b00000000, // "
		0b00100010, 0b01111111, 0b00100010, 0b11111111, 0b00100010, // #
		0b00100100, 0b00101010, 0b01111111, 0b00101010, 0b00010010, // $
		0b01000011, 0b00110011, 0b00001000, 0b01100110, 0b01100001, // %
		0b00110110, 0b01001001, 0b01001001, 0b00111100, 0b01001000, // &
		0b00000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000, // '
		0b00000000, 0b00000000, 0b00011100, 0b00100010, 0b01000001, // (
		0b01000001, 0b00100010, 0b00011100, 0b00000000, 0b00000000, // )
		0b00001000, 0b00111000, 0b00011100, 0b00111000, 0b00001000, // *
		0b00001000, 0b00001000, 0b00111110, 0b00001000, 0b00001000, // +
		0b01000000, 0b00100000, 0b00000000, 0b00000000, 0b00000000, // ,
		0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, // -
		0b01100000, 0b01100000, 0b00000000, 0b00000000, 0b00000000, // .
		0b01000000, 0b00110000, 0b00001000, 0b00000110, 0b00000001, // /
		0b00111110, 0b01000101, 0b01001001, 0b01010001, 0b00111110, // 0
		0b01000100, 0b01000010, 0b01111111, 0b01000000, 0b01000000, // 1
		0b01000110, 0b01100001, 0b01010001, 0b01001001, 0b01000110, // 2
		0b00100010, 0b01000001, 0b01001001, 0b01001001, 0b00110110, // 3
		0b00011000, 0b00010100, 0b00010010, 0b01111111, 0b00010000, // 4
		0b00100111, 0b01001001, 0b01001001, 0b01001001, 0b00110001, // 5
		0b00111110, 0b01001001, 0b01001001, 0b01001001, 0b00110000, // 6
		0b00000001, 0b00000001, 0b01110001, 0b00001001, 0b00000111, // 7
		0b00110110, 0b01001001, 0b01001001, 0b01001001, 0b00110110, // 8
		0b00000110, 0b01001001, 0b01001001, 0b01101001, 0b00111110, // 9
		0b00000000, 0b01100110, 0b01100110, 0b00000000, 0b00000000, // :
		0b00000000, 0b00100110, 0b01100110, 0b00000000, 0b00000000, // ;
		0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000, // <
		0b00101000, 0b00101000, 0b00101000, 0b00101000, 0b00101000, // =
		0b00000000, 0b01000001, 0b00100010, 0b00010100, 0b00001000, // >
		0b00000010, 0b00000001, 0b01010001, 0b00001001, 0b00000110, // ?
		0b00011110, 0b00100001, 0b01001101, 0b01010011, 0b00001110, // @
		0b01111110, 0b00001001, 0b00001001, 0b00001001, 0b01111110, // A
		0b01111111, 0b01001001, 0b01001001, 0b01101001, 0b00110110, // B
		0b00011100, 0b00100010, 0b01000001, 0b01000001, 0b01000001, // C
		0b01111111, 0b01000001, 0b01000001, 0b00100010, 0b00011100, // D
		0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01000001, // E
		0b01111111, 0b00001001, 0b00001001, 0b00001000, 0b00000001, // F
		0b00111110, 0b01000001, 0b01001001, 0b01001001, 0b00111010, // G
		0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, // H
		0b01000001, 0b01000001, 0b01111111, 0b01000001, 0b01000001, // I
		0b00100001, 0b01000001, 0b01000001, 0b01000001, 0b00111111, // J
		0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, // K
		0b01111111, 0b01000000, 0b01000000, 0b01000000, 0b01000000, // L
		0b01111111, 0b00000010, 0b00000100, 0b00000010, 0b01111111, // M
		0b01111111, 0b00000010, 0b00011100, 0b00100000, 0b01111111, // N
		0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, // O
		0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000110, // P
		0b00111110, 0b01000001, 0b01010001, 0b00100001, 0b01011110, // Q
		0b01111111, 0b00001001, 0b00011001, 0b00101001, 0b01000110, // R
		0b01000110, 0b01001001, 0b01001001, 0b01001001, 0b00110001, // S
		0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, // T
		0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b00111111, // U
		0b00011111, 0b00100000, 0b01000000, 0b00100000, 0b00011111, // V
		0b01111111, 0b00100000, 0b00010000, 0b00100000, 0b01111111, // W
		0b01000001, 0b00100010, 0b00011100, 0b00100010, 0b01000001, // X
		0b00000001, 0b00000010, 0b01111100, 0b00000010, 0b00000001, // Y
		0b01100001, 0b01010001, 0b01001001, 0b01000101, 0b01000011, // Z
		0b00000000, 0b01111111, 0b01000001, 0b01000001, 0b00000000, // [
		0b00000001, 0b00000110, 0b00001000, 0b00110000, 0b01000000, // \ <-
		0b00000000, 0b01000001, 0b01000001, 0b01111111, 0b00000000, // ]
		0b00000100, 0b00000010, 0b00000001, 0b00000010, 0b00000100, // ^
		0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, // _
		0b00000000, 0b00000001, 0b00000010, 0b00000100, 0b00000000, // `
		0b00110000, 0b01001000, 0b01001000, 0b01110000, 0b01000000, // a
		0b00000000, 0b00111110, 0b01001000, 0b01001000, 0b00110000, // b
		0b00000000, 0b00110000, 0b01001000, 0b01001000, 0b00000000, // c
		0b00000000, 0b00110000, 0b01001000, 0b01001000, 0b01111110, // d
		0b00000000, 0b00111000, 0b01010100, 0b01010100, 0b00011000, // e
		0b00000000, 0b00001000, 0b01111110, 0b00001010, 0b00000000, // f
		0b00000000, 0b01001100, 0b01010010, 0b01010010, 0b00111100, // g
		0b00000000, 0b01111110, 0b00001000, 0b00001000, 0b01110000, // h
		0b00000000, 0b00000000, 0b01110100, 0b00000000, 0b00000000, // i
		0b00000000, 0b00100000, 0b01000000, 0b00110100, 0b00000000, // j
		0b00000000, 0b01111100, 0b00100000, 0b01010000, 0b00000000, // k
		0b00000000, 0b00000010, 0b00111110, 0b01000000, 0b00000000, // l
		0b01110000, 0b00001000, 0b01110000, 0b00001000, 0b01110000, // m
		0b00000000, 0b01110000, 0b00001000, 0b00001000, 0b01110000, // n
		0b00000000, 0b00110000, 0b01001000, 0b01001000, 0b00110000, // o
		0b00000000, 0b01111000, 0b00100100, 0b00100100, 0b00011000, // p
		0b00000000, 0b00011000, 0b00100100, 0b00100100, 0b01111000, // q
		0b00000000, 0b01110000, 0b00001000, 0b00001000, 0b00000000, // r
		0b00000000, 0b01001000, 0b01010100, 0b01010100, 0b00100100, // s
		0b00000000, 0b00001000, 0b00111100, 0b01001000, 0b00000000, // t
		0b00000000, 0b00111000, 0b01000000, 0b01000000, 0b00111000, // u
		0b00010000, 0b00100000, 0b01000000, 0b00100000, 0b00010000, // v
		0b00111000, 0b01000000, 0b00110000, 0b01000000, 0b00111000, // w
		0b00000000, 0b01001000, 0b00110000, 0b01001000, 0b00000000, // x
		0b00000000, 0b01001100, 0b01010000, 0b00111100, 0b00000000, // y
		0b00000000, 0b01100100, 0b01010100, 0b01001100, 0b00000000, // z
		0b00000000, 0b00001000, 0b00111110, 0b01000001, 0b01000001, // {
		0b00000000, 0b00000000, 0b01111111, 0b00000000, 0b00000000, // |
		0b01000001, 0b01000001, 0b00111110, 0b00001000, 0b00000000, // }
		0b00001000, 0b00000100, 0b00001000, 0b00010000, 0b00001000, // ~
		// special characters
		0b00010000, 0b00111000, 0b01111100, 0b00111000, 0b00010000, // diamond
		0b01111000, 0b01001100, 0b01001010, 0b01001100, 0b01111000, // lock
		0b00010000, 0b00100000, 0b00010000, 0b00001000, 0b00000100, // tick
		0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, // cross
		0b01111111, 0b01000001, 0b01000001, 0b01000001, 0b01111111, // missing glyph
};

static uint8_t vram[512] = {0};
static uint8_t i2c_buf[2] = {0, 0};
static uint8_t text_x = 0, text_y = 0;

inline static uint8_t min(uint8_t x, uint8_t y) {
	return x > y ? y : x;
}

inline static uint8_t max(uint8_t x, uint8_t y) {
	return x > y ? x : y;
}

inline static int8_t sign(int8_t x) {
	return x > 0 ? 1 : (x < 0 ? -1 : 0);
}

inline static uint8_t babs(int8_t x) {
	return x > 0 ? x : -x;
}

void oled_init(I2C_HandleTypeDef *i) {
	HAL_Delay(100);
	i2c = i;
	for (uint8_t j = 0; j < 26; j++) {
		oled_write_command_byte(oled_init_command[j]);
	}
	oled_draw_box(1, 1, 126, 30);
	oled_draw_line(1, 1, 126, 30);
	oled_draw_line(126, 1, 1, 30);
	oled_update_screen();
	HAL_Delay(1500);
	oled_clear_screen();
	oled_move_caret(0, 0);
	oled_write_string("A quick brown fox jumped over the lazy dog.");
	oled_update_screen();
	HAL_Delay(1500);
}

void oled_write_command_byte(uint8_t command) {
	i2c_buf[0] = 0;
	i2c_buf[1] = command;
	HAL_I2C_Master_Transmit(i2c, OLED_WRITE_ADDR, i2c_buf, 2, HAL_MAX_DELAY);
}

void oled_write_data_byte(uint8_t data) {
	i2c_buf[0] = 0x40u;
	i2c_buf[1] = data;
	HAL_I2C_Master_Transmit(i2c, OLED_WRITE_ADDR, i2c_buf, 2, HAL_MAX_DELAY);
}

void oled_update_screen() {
	oled_write_command_byte(OLED_SET_COL_ADDR);
	oled_write_command_byte(0);
	oled_write_command_byte(127);
	oled_write_command_byte(OLED_SET_PAGE_ADDR);
	oled_write_command_byte(0);
	oled_write_command_byte(3);
	for (uint32_t i = 0; i < 512; i++) {
		oled_write_data_byte(vram[i]);
	}
}

void oled_clear_screen() {
	memset(vram, 0, 512);
}

void oled_draw_pixel(uint8_t x, uint8_t y) {
	uint16_t offset = x + y / 8 * 128;
	vram[offset] |= (1u << (y % 8u));
}

void oled_draw_line(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
	if (y0 == y1) {
		oled_draw_horizontal_line(x0, x1, y0);
	} else if (x0 == x1) {
		oled_draw_vertical_line(y0, y1, x0);
	} else {
		uint8_t cx = x0;
		uint8_t cy = y0;
		int8_t sx = sign(x1 - x0);
		int8_t sy = sign(y1 - y0);
		int8_t slope = (x1 - x0) / (y1 - y0);
		uint8_t count = 0;
		while (cx != x1) {
			oled_draw_pixel(cx, cy);
			if (++count >= babs(slope)) {
				count = 0;
				cy += sy;
			}
			cx += sx;
		}
	}
}

void oled_draw_horizontal_line(uint8_t x0, uint8_t x1, uint8_t y) {
	uint8_t cx = min(x0, x1);
	uint8_t sx = max(x0, x1);
	uint16_t offset = cx + y / 8 * 128;
	uint8_t mask = (1u << (y % 8u));
	for (uint8_t i = 0; i < sx - cx; i++) {
		vram[offset + i] |= mask;
	}
}

void oled_draw_vertical_line(uint8_t y0, uint8_t y1, uint8_t x) {
	uint8_t cy = min(y0, y1);
	uint8_t sy = max(y0, y1);
	uint16_t offset = x + cy / 8 * 128, endOffset = x + sy / 8 * 128;
	// test if crosses the byte bound
	if (sy - cy > 8 || (cy / 8 != sy / 8)) {
		// yes
		vram[offset] |= bitmask_lut[cy % 8][7];
		for (uint8_t i = 1; i < (sy - cy) / 8; i++) {
			vram[offset + 128 * i] = 0xffu;
		}
		vram[endOffset] |= bitmask_lut[0][sy % 8];
	} else {
		uint8_t mask = bitmask_lut[cy % 8][sy % 8];
		vram[offset] |= mask;
	}
}

void oled_draw_box(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
	oled_draw_horizontal_line(x0, x1, y0);
	oled_draw_horizontal_line(x0, x1, y1);
	oled_draw_vertical_line(y0, y1, x0);
	oled_draw_vertical_line(y0, y1, x1);
}

void oled_write_char(uint8_t x, uint8_t y, char c) {
	uint16_t offset = x + y / 8 * 128;
	uint8_t lutOffset;
	if (c < ' ' || c > '~') {
		switch (c) {
			case '\a':
				lutOffset = 95;
				break;
			case '\b':
				lutOffset = 96;
				break;
			case '\t':
				lutOffset = 97;
				break;
			case '\v':
				lutOffset = 98;
				break;
			default:
				lutOffset = 99;
				break;
		}
	} else {
		lutOffset = c - ' ';
	}
	for (uint8_t i = 0; i < 5; i++) {
		vram[offset + i] = font[lutOffset][i];
	}
}

void oled_move_caret(uint8_t row, uint8_t col) {
	text_y = (row * 8) % 32;
	text_x = (col * 6) % 128;
}

void oled_write_escaped_char(char c) {
	if (c == '\r') {
		text_x = 0;
	} else if (c == '\n') {
		text_y += 8;
		if (text_y >= 32) {
			text_y = 0; // wrap over
		}
	} else {
		oled_write_char(text_x, text_y, c);
		text_x += 6;
		if (text_x + 5 >= 128) {
			text_x = 0;
			text_y += 8;
			if (text_y >= 32) {
				text_y = 0;
			}
		}
	}
}

void oled_write_string(const char *c) {
	char *p = (char *) c;
	while (*p != 0) {
		oled_write_escaped_char(*p);
		++p;
	}
}

void oled_write_length_string(const char *str, uint8_t len) {
	for (uint8_t i = 0; i < len && str[i] > 0; i++) {
		oled_write_escaped_char(str[i]);
	}
}

void oled_write_bytes(const uint8_t *bytes, uint8_t len) {
	for (uint8_t i = 0; i < len; i++) {
		oled_write_escaped_char(nibble_to_hex(bytes[i] >> 8u));
		oled_write_escaped_char(nibble_to_hex(bytes[i] & 0x0fu));
	}
}

void oled_clear_row(uint8_t row) {
	uint8_t offset = ((row * 8) % 32) / 8 * 128;
	memset(vram + offset, 0, 128);
}
